package com.tzy.springboot.aspect;

import com.alibaba.fastjson.JSON;
import com.tzy.springboot.entity.SystemLog;
import com.tzy.springboot.service.ISystemLogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 系统日志切面
 * 自动记录所有 Controller 方法的执行情况
 *
 * @Aspect: 标记这是一个切面类
 * @Component: 将此类注册为 Spring Bean
 */
@Aspect
@Component
public class SystemLogAspect {

    @Autowired
    private ISystemLogService systemLogService;

    /**
     * 定义切入点：匹配所有 Controller 包下的所有方法
     * 语法解释：
     * execution(返回值 包名.类名.方法名(参数))
     * * = 任意
     * .. = 任意子包或任意参数
     */
    @Pointcut("execution(* com.tzy.springboot.controller..*.*(..))")
    public void controllerPointcut() {
        // 这个方法体为空，只是用来定义切入点
    }

    /**
     * 环绕通知：在方法执行前后都可以执行自定义逻辑
     * @param point 切入点对象，包含了方法的所有信息
     */
    @Around("controllerPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 记录开始时间
        long startTime = System.currentTimeMillis();

        // 创建日志对象
        SystemLog log = new SystemLog();
        log.setCreateTime(LocalDateTime.now());

        try {
            // ========== 1. 记录请求信息 ==========
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                log.setIp(request.getRemoteAddr());  // 记录 IP 地址
            }

            // ========== 2. 记录方法信息 ==========
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            String className = point.getTarget().getClass().getSimpleName();
            String methodName = method.getName();

            log.setModule(className);  // 模块名（Controller 类名）
            log.setMethod(className + "." + methodName);  // 完整方法名
            log.setOperation(methodName);  // 操作名

            // ========== 3. 记录参数 ==========
            Object[] args = point.getArgs();
            if (args != null && args.length > 0) {
                try {
                    // 将参数转为 JSON 字符串
                    log.setParams(JSON.toJSONString(args));
                } catch (Exception e) {
                    // 如果转换失败（比如参数不能序列化），直接用 toString
                    log.setParams(args.toString());
                }
            }

            // ========== 4. 执行目标方法 ==========
            Object result = point.proceed();  // 这里才真正执行原方法

            // ========== 5. 记录成功日志 ==========
            long executionTime = System.currentTimeMillis() - startTime;
            log.setLevel("INFO");
            log.setExecutionTime(executionTime);
            log.setContent("执行成功");

            // 保存日志到数据库
            systemLogService.saveLog(log);

            return result;  // 返回原方法的结果

        } catch (Throwable e) {
            // ========== 6. 记录错误日志 ==========
            long executionTime = System.currentTimeMillis() - startTime;
            log.setLevel("ERROR");
            log.setExecutionTime(executionTime);
            log.setContent("执行失败: " + e.getMessage());
            log.setExceptionInfo(getStackTrace(e));  // 记录异常堆栈

            // 保存错误日志
            systemLogService.saveLog(log);

            throw e;  // 重新抛出异常，不影响原业务逻辑
        }
    }

    /**
     * 获取异常堆栈信息（前10行）
     */
    private String getStackTrace(Throwable e) {
        StringBuilder sb = new StringBuilder();
        sb.append(e.toString()).append("\n");
        StackTraceElement[] traces = e.getStackTrace();
        // 只记录前10行，避免日志太长
        for (int i = 0; i < Math.min(traces.length, 10); i++) {
            sb.append("\tat ").append(traces[i]).append("\n");
        }
        return sb.toString();
    }
}
